package com.sl.ms.carriage.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.stream.StreamUtil;
import cn.hutool.core.util.*;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sl.ms.base.api.common.AreaFeign;
import com.sl.ms.base.domain.base.AreaDto;
import com.sl.ms.carriage.domain.constant.CarriageConstant;
import com.sl.ms.carriage.domain.dto.CarriageDTO;
import com.sl.ms.carriage.domain.dto.WaybillDTO;
import com.sl.ms.carriage.domain.enums.EconomicRegionEnum;
import com.sl.ms.carriage.entity.CarriageEntity;
import com.sl.ms.carriage.enums.CarriageExceptionEnum;
import com.sl.ms.carriage.mapper.CarriageMapper;
import com.sl.ms.carriage.service.CarriageService;
import com.sl.ms.carriage.utils.CarriageUtils;
import com.sl.transport.common.exception.SLException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.weaver.ast.Var;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.stream.Collectors;


@Service
@Slf4j
public class CarriageServiceImpl extends ServiceImpl<CarriageMapper, CarriageEntity> implements CarriageService {

    @Resource
    private AreaFeign areaFeign;


    /**
     * 获取运费模版列表
     *
     * @return 运费模版列表
     */
    @Override
    public List<CarriageDTO> findAll() {
        //1.构造查询条件 ,按照创建时间排序
        LambdaQueryWrapper<CarriageEntity> wrapper = Wrappers.<CarriageEntity>lambdaQuery()
                .orderByDesc(CarriageEntity::getCreated);
        //查询数据库
        List<CarriageEntity> list = super.list(wrapper);
        List<CarriageDTO> carriageDTOS = CollStreamUtil.toList(list, CarriageUtils::toDTO);
        return carriageDTOS;
    }

    /**
     * 新增/修改运费对象
     *
     * @param carriageDto 新增/修改运费对象
     *                    必填字段：templateType、transportType
     *                    更新时传入id字段
     * @return
     */
    @Override
    @Transactional
    public CarriageDTO saveOrUpdate(CarriageDTO carriageDto) {
        //1.设置查询条件，查询运费模版列表
        LambdaQueryWrapper<CarriageEntity> wrapper = Wrappers.<CarriageEntity>lambdaQuery()
                .eq(CarriageEntity::getTemplateType, carriageDto.getTemplateType())
                .eq(CarriageEntity::getTransportType, CarriageConstant.REGULAR_FAST);
        List<CarriageEntity> list = super.list(wrapper);
        //2.判断是否为经济特区，如果不是，需要进一步的判断是否重复，如果是，需要判断关联城市是否重复
        if (ObjectUtil.notEqual(carriageDto.getTemplateType(), CarriageConstant.ECONOMIC_ZONE)) {
            //模版存在，且是新增操作，直接抛出异常
            if (CollUtil.isNotEmpty(list) && ObjectUtil.isEmpty(carriageDto.getId())) {
                //说明数据库中，该类型的模版已经存在，并且此次操作为新增，则抛出异常（模版已经存在，不能再新增）
                throw new SLException(CarriageExceptionEnum.NOT_ECONOMIC_ZONE_REPEAT);
            }
            // 模版存在，不是新增操作,更新数据
            //更新时判断是否已经存在该类型的模版，模版不匹配，无法更新，抛出异常
            long count = StreamUtil.of(list)
                    .filter(carriageEntity -> ObjectUtil.notEqual(carriageEntity.getId(), carriageDto.getId()))
                    .count();
            if (count > 0) {
                throw new SLException(CarriageExceptionEnum.NOT_ECONOMIC_ZONE_REPEAT);
            }
            //更新模版操作
            return this.saveOrUpdateCarriage(carriageDto);
        }
        //3.是经济区，校验关联城市是否重复
        if (CollUtil.isEmpty(list)) {
            //直接新增或更新
            return this.saveOrUpdateCarriage(carriageDto);
        }
        //判断重复的思路，先将查询出的运费模版中的关联城市收集起来，传入的关联集合是否在此集合中
        //查询出其他模版中的所有经济区列表
        List<String> associatedCityList = StreamUtil.of(list)
                //排除掉自己，检查与其他模版是否存在冲突
                .filter(carriageEntity -> ObjectUtil.notEqual(carriageEntity.getId(), carriageDto.getId()))
                //获取关联城市
                .map(CarriageEntity::getAssociatedCity)
                //将关联城市按照逗号分隔
                .map(associatedCity -> StrUtil.split(associatedCity, ','))
                //将上面得到的集合展开，得到字符串
                .flatMap(StreamUtil::of)
                //收集到集合中
                .collect(Collectors.toList());
        //取交集，如果存在交集，说明重复
        Collection<String> intersection = CollUtil.intersection(associatedCityList, carriageDto.getAssociatedCityList());
        if (CollUtil.isNotEmpty(intersection)) {
            //有交集，说明有重复
            throw new SLException(CarriageExceptionEnum.ECONOMIC_ZONE_CITY_REPEAT);
        }
        //没有重复
        return this.saveOrUpdateCarriage(carriageDto);
    }

    /**
     * 运费计算
     *
     * @param waybillDTO 运费计算对象
     * @return 运费模板对象，不仅包含模板数据还包含：computeWeight、expense 字段
     */
    @Override
    public CarriageDTO compute(WaybillDTO waybillDTO) {
        //1.根据参数查找运费模版，查找顺序 同城 > 省内 > 经济区互寄 > 跨省
        CarriageEntity carriage = this.findCarriage(waybillDTO);
        //2.计算实际计费重量，确保最小重量为1kg
        //规则：不满1kg，按1kg计费；10kg以下续重以0.1kg计量保留1位小数；10-100kg续重以0.5kg计量保留1位小数；100kg以上四舍五入取整
        double computeWeight = this.getComputeWeight(waybillDTO, carriage);
        //3.计算运费，首重+ 续重，保留一位小数
        double expense = carriage.getFirstWeight() + ((computeWeight - 1) * carriage.getContinuousWeight());
        expense=NumberUtil.round(expense,1).doubleValue();

        //4.封装对象返回
        CarriageDTO carriageDTO = CarriageUtils.toDTO(carriage);
        carriageDTO.setExpense(expense);
        carriageDTO.setComputeWeight(computeWeight);
        return carriageDTO;
    }

    /**
     * 根据模板类型查询模板，经济区互寄不通过该方法查询模板
     *
     * @param templateType 模板类型：1-同城寄，2-省内寄，4-跨省
     * @return 运费模板
     */
    @Override
    public CarriageEntity findByTemplateType(Integer templateType) {
        //经济区互寄不通过该方法查询模板
        if (ObjectUtil.equals(templateType, CarriageConstant.ECONOMIC_ZONE)) {
            throw new SLException(CarriageExceptionEnum.METHOD_CALL_ERROR);
        }
        LambdaQueryWrapper<CarriageEntity> wrapper = Wrappers.<CarriageEntity>lambdaQuery()
                .eq(CarriageEntity::getTemplateType, templateType);
        return this.getOne(wrapper);
    }

    /**
     * 新增或更新模版操作
     *
     * @param carriageDto 运费模版dto对象
     * @return
     */
    private CarriageDTO saveOrUpdateCarriage(CarriageDTO carriageDto) {
        CarriageEntity carriageEntity = CarriageUtils.toEntity(carriageDto);
        boolean result = super.saveOrUpdate(carriageEntity);
        if (result) {
            return CarriageUtils.toDTO(carriageEntity);
        }
        throw new SLException(CarriageExceptionEnum.SAVE_OR_UPDATE_ERROR);
    }

    /**
     * 查找运费模版
     *
     * @param waybillDTO 运费计算参数对象
     * @return
     */
    public CarriageEntity findCarriage(WaybillDTO waybillDTO) {
        //根据参数查找运费模版，查找顺序 同城 > 省内 > 经济区互寄 > 跨省
        //1.校验是否是同城
        //收件人和发件人的城市id相同，则是同城
        if (ObjectUtil.equals(waybillDTO.getReceiverCityId(), waybillDTO.getSenderCityId())) {
            CarriageEntity carriageEntity = this.findByTemplateType(CarriageConstant.SAME_CITY);
            if (ObjectUtil.isNotEmpty(carriageEntity)) {
                return carriageEntity;
            }
        }
        //2.校验是否是省内
        //2.1获取收寄件地址省份id
        Long receiverProvinceId = this.areaFeign.get(waybillDTO.getReceiverCityId()).getParentId();
        Long senderProvinceId = this.areaFeign.get(waybillDTO.getSenderCityId()).getParentId();
        if (ObjectUtil.equals(receiverProvinceId, senderProvinceId)) {
            //2.2查询同省运费模版
            CarriageEntity carriageEntity = this.findByTemplateType(CarriageConstant.SAME_PROVINCE);
            if (ObjectUtil.isNotEmpty(carriageEntity)) {
                return carriageEntity;
            }
        }

        //3.校验是否为经济特区互寄
        //3.1获取经济区城市配置枚举
        LinkedHashMap<String, EconomicRegionEnum> economicRegionMap = EnumUtil.getEnumMap(EconomicRegionEnum.class);
        EconomicRegionEnum economicRegionEnum = null;
        for (EconomicRegionEnum regionEnum : economicRegionMap.values()) {
            //该经济区是否全部包含收发件人省id
            boolean result = ArrayUtil.containsAll(regionEnum.getValue(), receiverProvinceId, senderProvinceId);
            if (result) {
                economicRegionEnum = regionEnum;
            }
        }
        if (ObjectUtil.isNotEmpty(economicRegionEnum)) {
            //3.2根据类型编码查询
            assert economicRegionEnum != null;
            LambdaQueryWrapper<CarriageEntity> wrapper = Wrappers.<CarriageEntity>lambdaQuery()
                    .eq(CarriageEntity::getTemplateType, CarriageConstant.ECONOMIC_ZONE)
                    .eq(CarriageEntity::getTransportType, CarriageConstant.REGULAR_FAST)
                    .like(CarriageEntity::getAssociatedCity, economicRegionEnum.getCode());
            CarriageEntity carriage = super.getOne(wrapper);
            if (ObjectUtil.isNotEmpty(carriage)) {
                return carriage;
            }
        }
        //4.跨省
        return this.findByTemplateType(CarriageConstant.TRANS_PROVINCE);
    }

    /**
     * 根据体积参数与实际重量 计算计费重量
     *
     * @param waybillDTO     运费计算对象
     * @param carriageEntity 运费模版
     * @return 计费重量
     */
    public double getComputeWeight(WaybillDTO waybillDTO, CarriageEntity carriageEntity) {
        //计算体积，如果传入体积不需要计算
        Integer volume = waybillDTO.getVolume();
        if (ObjectUtil.isEmpty(volume)) {
            try {
                //长* 宽* 高 计算体积
                volume = waybillDTO.getMeasureLong() * waybillDTO.getMeasureWidth() * waybillDTO.getMeasureHigh();
            } catch (Exception e) {
                //计算出错，设置体积为0
                volume = 0;
            }
        }
        //计算体积重量，体积/轻抛系数
        BigDecimal volumeWeight = NumberUtil.div(volume, carriageEntity.getLightThrowingCoefficient(), 1);
        //取最大值
        double computeWeight = NumberUtil.max(volumeWeight.doubleValue(), NumberUtil.round(waybillDTO.getWeight(), 1).doubleValue());
        //计算续重，规则：不满1kg，按1kg计费；10kg以下续重以0.1kg计量保留1位小数；10-100kg续重以0.5kg计量保留1位小数；100kg以上四舍五入取整
        //不满1kg，按1kg计费
        if (computeWeight <= 1) {
            return 1;
        }
        //10kg以内
        if (computeWeight <= 10) {
            return computeWeight;
        }
        //100kg以上四舍五入取整
        if (computeWeight > 100) {
            return NumberUtil.round(computeWeight, 0).doubleValue();
        }
        //举例：
        //以0.5作为一个计算单位
        //18.8kg按照19kg计费
        //18.4kg按照18.5kg计费
        //18.1kg按照18.5kg计费

        //向下舍入
        int integer = NumberUtil.round(computeWeight, 0, RoundingMode.DOWN).intValue();
        double sub = NumberUtil.sub(computeWeight, integer);
        if (sub == 0) {
            return integer;
        }
        if (sub < 0.5) {
            return NumberUtil.add(integer, 0.5);
        }
        //如果 0.5 <= sub <= 1
        return NumberUtil.add(integer, 1);
    }


}
